﻿namespace MicroCloud.Systems
{
    /// <summary>
    /// 文件管理器基类
    /// </summary>
    public abstract class FileStoreBase<TFile, TFileKey,
        TFileActionRecord, TFileActionRecordKey,
        TFileSaveRecord, TFileSaveRecordKey, TUserKey> : Disposable,
        IFileStore<TFile>, IFileActionRecordStore<TFileActionRecord>, IFileSaveRecordStore<TFileSaveRecord>
        where TFile : FileBase<TFileKey, TUserKey>
        where TFileKey : struct, IEquatable<TFileKey>
        where TFileActionRecord : FileActionRecordBase<TFileActionRecordKey, TFileKey, TUserKey>
        where TFileActionRecordKey : struct, IEquatable<TFileActionRecordKey>
        where TFileSaveRecord : FileSaveRecordBase<TFileSaveRecordKey, TFileKey, TUserKey>
        where TFileSaveRecordKey : struct, IEquatable<TFileSaveRecordKey>
        where TUserKey : struct, IEquatable<TUserKey>
    {
        private readonly IServiceProvider _provider;

        /// <summary>
        /// 初始化一个文件管理器基类
        /// </summary>
        /// <param name="provider">服务提供程序</param>
        protected FileStoreBase(IServiceProvider provider)
        {
            _provider = provider;
        }

        #region 属性
        /// <summary>
        /// 获取 事件总线
        /// </summary>
        protected IEventBus EventBus => _provider.GetService<IEventBus>();

        /// <summary>
        /// 获取 文件仓储
        /// </summary>
        protected IRepository<TFile, TFileKey> FileRepository => _provider.GetService<IRepository<TFile, TFileKey>>();

        /// <summary>
        /// 获取 文件操作仓储
        /// </summary>
        protected IRepository<TFileActionRecord, TFileActionRecordKey> FileActionRecordRepository => _provider.GetService<IRepository<TFileActionRecord, TFileActionRecordKey>>();

        /// <summary>
        /// 获取 文件存档仓储
        /// </summary>
        protected IRepository<TFileSaveRecord, TFileSaveRecordKey> FileSaveRecordRepository => _provider.GetService<IRepository<TFileSaveRecord, TFileSaveRecordKey>>();
        #endregion

        #region IFileStore
        public IQueryable<TFile> Files => FileRepository.QueryAsNoTracking();

        public virtual TFile FindById(string fileId)
        {
            ThrowIfDisposed();
            Check.NotNullOrEmpty(fileId, nameof(fileId));

            TFileKey id = ConvertIdFromString(fileId);
            var fileEntity = FileRepository.QueryAsNoTracking(m => Equals(m.Id, id)).FirstOrDefault();
            return fileEntity;
        }
        public virtual async Task<TFile> FindByIdAsync(string fileId, CancellationToken cancellationToken = new CancellationToken())
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();
            Check.NotNullOrEmpty(fileId, nameof(fileId));

            TFileKey id = ConvertIdFromString(fileId);
            var fileEntity = FileRepository.QueryAsNoTracking(m => Equals(m.Id, id)).FirstOrDefault();
            return await Task.FromResult(fileEntity);
        }

        public virtual bool CheckFileExists(Expression<Func<TFile, bool>> predicate, string fileId = default)
        {
            ThrowIfDisposed();
            return FileRepository.CheckExists(predicate, ConvertIdFromString(fileId));
        }
        public virtual async Task<bool> CheckFileExistsAsync(Expression<Func<TFile, bool>> predicate, string fileId = default, CancellationToken cancellationToken = new CancellationToken())
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            return await FileRepository.CheckExistsAsync(predicate, ConvertIdFromString(fileId));
        }

        public virtual OperationResult Create(TFile fileEntity)
        {
            ThrowIfDisposed();

            return FileRepository.Insert(fileEntity);
        }
        public virtual async Task<OperationResult> CreateAsync(TFile fileEntity, CancellationToken cancellationToken = new CancellationToken())
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            return await FileRepository.InsertAsync(fileEntity);
        }

        public virtual OperationResult Update(TFile fileEntity)
        {
            ThrowIfDisposed();

            return FileRepository.Update(fileEntity);
        }
        public virtual async Task<OperationResult> UpdateAsync(TFile fileEntity, CancellationToken cancellationToken = new CancellationToken())
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            return await FileRepository.UpdateAsync(fileEntity);
        }

        public virtual OperationResult Delete(TFile fileEntity)
        {
            ThrowIfDisposed();

            return FileRepository.Delete(fileEntity);
        }
        public virtual async Task<OperationResult> DeleteAsync(TFile fileEntity, CancellationToken cancellationToken = new CancellationToken())
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            return await FileRepository.DeleteAsync(fileEntity);
        }
        #endregion

        #region IFileActionRecordStore

        public virtual OperationResult AddActionRecord(TFileActionRecord fileActionRecord)
        {
            ThrowIfDisposed();

            return FileActionRecordRepository.Insert(fileActionRecord);
        }

        public virtual async Task<OperationResult> AddActionRecordAsync(TFileActionRecord fileActionRecord, CancellationToken cancellationToken = new CancellationToken())
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            return await FileActionRecordRepository.InsertAsync(fileActionRecord);
        }
        #endregion

        #region IFileSaveRecordStore
        public virtual OperationResult AddSaveRecord(TFileSaveRecord entity)
        {
            ThrowIfDisposed();

            return FileSaveRecordRepository.Insert(entity);
        }

        public virtual async Task<OperationResult> AddSaveRecordAsync(TFileSaveRecord entity, CancellationToken cancellationToken = new CancellationToken())
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            return await FileSaveRecordRepository.InsertAsync(entity);
        }
        #endregion

        #region 辅助方法
        /// <summary>
        /// 将提供的<paramref name="id"/>转换为强类型键对象
        /// </summary>
        /// <param name="id">要转换的id</param>
        /// <returns><typeparamref name="TFileKey"/>的实例表示提供的<paramref name="id"/></returns>
        public virtual TFileKey ConvertIdFromString(string id)
        {
            if (id == null)
            {
                return default;
            }

            return (TFileKey)TypeDescriptor.GetConverter(typeof(TFileKey)).ConvertFromInvariantString(id);
        }

        /// <summary>
        /// 将提供的<paramref name="id"/>转换为其字符串表示形式
        /// </summary>
        /// <param name="id">要转换的id</param>
        /// <returns>提供<paramref name="id"/>的<see cref="string"/>的表示</returns>
        public virtual string ConvertIdToString(TFileKey id)
        {
            if (id.Equals(default))
            {
                return null;
            }

            return id.ToString();
        }

        /// <summary>
        /// 如果已释放，则抛出异常
        /// </summary>
        protected void ThrowIfDisposed()
        {
            if (Disposed)
            {
                throw new ObjectDisposedException(GetType().Name);
            }
        }
        #endregion
    }
}